using System;
using System.Collections.Generic;
using System.Net;
using CookComputing.XmlRpc;
using NMollom.Internal;

namespace NMollom {
    [Serializable]
    public class Mollom {
        private readonly Authentication rootAuth;
        private readonly List<string> servers;

        public Mollom(string publicKey, string privateKey) {
            if (string.IsNullOrEmpty(publicKey))
                throw new ArgumentNullException("publicKey");
            if (string.IsNullOrEmpty(privateKey))
                throw new ArgumentNullException("privateKey");

            rootAuth = new Authentication(publicKey, privateKey);
            servers = new List<string>();
        }

        public string PublicKey {
            get { return rootAuth.PublicKey; }
        }

        public string PrivateKey {
            get { return rootAuth.PrivateKey; }
        }

        public IEnumerable<string> Servers {
            get {
                if (servers.Count <= 1) {
                    IMollomProxy proxy = GetProxy();
                    servers.Clear();
                    servers.AddRange(proxy.GetServerList(new Params(rootAuth.Next())));

                    // always add this one as a backup
                    servers.Add("http://xmlrpc.mollom.com");
                }
                return servers;
            }
        }

        #region Mollom API
        /// <summary>
        /// Returns the API version of the Mollom endpoint we'll use.
        /// This is equivalent to the major.minor version of the NMollom assembly.
        /// </summary>
        public static string ApiVersion {
            get { return typeof (Mollom).Assembly.GetName().Version.ToString(2); }
        }

        public string[] GetServerList() {
            return
                TryAllServers<string[]>(
                    delegate(IMollomProxy useProxy, Authentication auth) { return useProxy.GetServerList(new Params(auth)); });
        }

        public bool VerifyKey() {
            return
                TryAllServers<bool>(
                    delegate(IMollomProxy useProxy, Authentication auth) { return useProxy.VerifyKey(new Params(auth)); });
        }

        public ContentAssesment CheckContent(string postTitle, string postBody) {
            return CheckContent(postTitle, postBody, "", "", "", "", "", null);
        }

        public ContentAssesment CheckContent(string postTitle, string postBody, ContentAssesment previousAssesment) {
            return CheckContent(postTitle, postBody, "", "", "", "", "", null, previousAssesment);
        }

        public ContentAssesment CheckContent(string postTitle, string postBody, Captcha previousCaptcha) {
            return CheckContent(postTitle, postBody, "", "", "", "", "", null, previousCaptcha);
        }

        public ContentAssesment CheckContent(string postTitle, string postBody, string authorName, string authorMail) {
            return CheckContent(postTitle, postBody, authorName, authorMail, "", "", "", null);
        }

        public ContentAssesment CheckContent(string postTitle, string postBody, string authorName, string authorMail, ContentAssesment previousAssesment) {
            return CheckContent(postTitle, postBody, authorName, authorMail, "", "", "", null, previousAssesment);
        }

        public ContentAssesment CheckContent(string postTitle, string postBody, string authorName, string authorMail, Captcha previousCaptcha) {
            return CheckContent(postTitle, postBody, authorName, authorMail, "", "", "", null, previousCaptcha);
        }

        public ContentAssesment CheckContent(string postTitle, string postBody, string authorName, string authorMail, string authorUrl) {
            return CheckContent(postTitle, postBody, authorName, authorMail, authorUrl, "", "", null);
        }

        public ContentAssesment CheckContent(string postTitle, string postBody, string authorName, string authorMail, string authorUrl, ContentAssesment previousAssesment) {
            return CheckContent(postTitle, postBody, authorName, authorMail, authorUrl, "", "", null, previousAssesment);
        }

        public ContentAssesment CheckContent(string postTitle, string postBody, string authorName, string authorMail, string authorUrl, Captcha previousCaptcha) {
            return CheckContent(postTitle, postBody, authorName, authorMail, authorUrl, "", "", null, previousCaptcha);
        }

        public ContentAssesment CheckContent(string postTitle, string postBody, string authorOpenId) {
            return CheckContent(postTitle, postBody, "", "", "", authorOpenId, "", null);
        }

        public ContentAssesment CheckContent(string postTitle, string postBody, string authorName, string authorMail, string authorUrl, string authorOpenId, string authorId, IPAddress authorIp) {
            return CheckContent(postTitle, postBody, authorName, authorMail, authorUrl, authorOpenId, authorId, authorIp, "");
        }

        public ContentAssesment CheckContent(string postTitle, string postBody, string authorName, string authorMail, string authorUrl, string authorOpenId, string authorId, IPAddress authorIp, ContentAssesment previousAssesment) {
            return CheckContent(postTitle, postBody, authorName, authorMail, authorUrl, authorOpenId, authorId, authorIp,
                previousAssesment != null ? previousAssesment.SessionId : "");
        }

        public ContentAssesment CheckContent(string postTitle, string postBody, string authorName, string authorMail, string authorUrl, string authorOpenId, string authorId, IPAddress authorIp, Captcha previousCaptcha) {
            return CheckContent(postTitle, postBody, authorName, authorMail, authorUrl, authorOpenId, authorId, authorIp,
                previousCaptcha != null ? previousCaptcha.SessionId : "");
        }

        protected ContentAssesment CheckContent(string postTitle, string postBody, string authorName, string authorMail, string authorUrl, string authorOpenId, string authorId, IPAddress authorIp, string sessionId) {
            ContentCheckResult r = TryAllServers<ContentCheckResult>(
                delegate(IMollomProxy useProxy, Authentication auth) {
                    CheckContentParams p = new CheckContentParams(auth);
                    p.authorId = authorId;
                    p.authorIp = authorIp == null ? "" : authorIp.ToString();
                    p.authorMail = authorMail;
                    p.authorName = authorName;
                    p.authorUrl = authorUrl;
                    p.authorOpenId = authorOpenId;
                    p.postTitle = postTitle;
                    p.postBody = postBody;
                    p.sessionId = sessionId;
                    return useProxy.CheckContent(p);
                });

            return new ContentAssesment(r, this);
        }

        public void SendFeedback(ContentAssesment assesment, FeedbackType type) {
            TryAllServers<bool>(
                delegate(IMollomProxy useProxy, Authentication auth) {
                    return useProxy.SendFeedback(new SendFeedbackParams(auth, assesment, type));
                });
        }

        public ImageCaptcha GetImageCaptcha() {
            return GetImageCaptcha(null);
        }

        public ImageCaptcha GetImageCaptcha(IPAddress authorIp) {
            return GetImageCaptcha(null, authorIp);
        }

        public ImageCaptcha GetImageCaptcha(ContentAssesment previousAssesment, IPAddress authorIp) {
            CaptchaResult r = TryAllServers<CaptchaResult>(
                delegate(IMollomProxy useProxy, Authentication auth) {
                    return useProxy.GetImageCaptcha(new GetCaptchaParams(auth, previousAssesment, authorIp));
                });

            return new ImageCaptcha(r, this);
        }

        public AudioCaptcha GetAudioCaptcha() {
            return GetAudioCaptcha(null);
        }

        public AudioCaptcha GetAudioCaptcha(IPAddress authorIp) {
            return GetAudioCaptcha(null, authorIp);
        }

        public AudioCaptcha GetAudioCaptcha(ContentAssesment previousAssesment, IPAddress authorIp) {
            CaptchaResult r = TryAllServers<CaptchaResult>(
                delegate(IMollomProxy useProxy, Authentication auth) {
                    return useProxy.GetAudioCaptcha(new GetCaptchaParams(auth, previousAssesment, authorIp));
                });

            return new AudioCaptcha(r, this);
        }

        public bool CheckCaptcha(Captcha captcha, string solution) {
            return TryAllServers<bool>(
                delegate(IMollomProxy useProxy, Authentication auth) {
                    CheckCaptchaParams p = new CheckCaptchaParams(auth, captcha, solution);
                    return useProxy.CheckCaptcha(p);
                });
        }

        /// <summary>
        /// Gets Mollom statistic value.
        /// </summary>
        /// <param name="type">The type of statistic to collect.</param>
        /// <returns>An integer representing </returns>
        public int GetStatistics(StatisticType type) {
            return TryAllServers<int>(
                delegate(IMollomProxy useProxy, Authentication auth) { return useProxy.GetStatistics(new GetStatisticsParams(auth, type)); });
        }

        /// <summary>
        /// Returns all Mollom statistics in one call. This actually performs 
        /// several XMLRPC calls to the mollom server, so if you only need one 
        /// statistic, it's better to use the GetStatistics() method instead of this one.
        /// </summary>
        /// <returns>A Statistics object with the collected statistics.</returns>
        public Statistics GetAllStatistics() {
            return new Statistics(
                GetStatistics(StatisticType.TotalDays),
                GetStatistics(StatisticType.TotalAccepted),
                GetStatistics(StatisticType.TotalRejected),
                GetStatistics(StatisticType.YesterdayAccepted),
                GetStatistics(StatisticType.YesterdayRejected),
                GetStatistics(StatisticType.TodayAccepted),
                GetStatistics(StatisticType.TodayRejected)
                );
        }
        #endregion

        #region Private Methods

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="call"></param>
        /// <returns></returns>
        private T TryAllServers<T>(MollomCall<T> call) {
Restart:
            foreach (string server in Servers) {
                try {
                    IMollomProxy proxy = GetProxy(server);
                    return call(proxy, rootAuth.Next());
                }
                catch(XmlRpcFaultException e) {
                    const int MollomError = 1000;
                    const int MollomRefresh = 1100;
                    const int MollomBusy = 1200;

                    switch (e.FaultCode) {
                        case MollomError:
                            throw new MollomException(e.FaultCode, e.FaultString);
                        case MollomRefresh:
                            servers.Clear();
                            goto Restart;
                        case MollomBusy:
                            // next server, do nothing
                            break;
                        default:
                            throw new MollomException(e.FaultCode, e.FaultString);
                    }
                }
            }

            // all fails, reset list to load a new one.
            servers.Clear();
            throw new InvalidProgramException("Shouldn't get here");
        }

        /// <summary>
        /// Gets the XML-RPC proxy object. Fills in certain header values automatically.
        /// </summary>
        /// <returns>An IMollomProxy object.</returns>
        private static IMollomProxy GetProxy() {
            return GetProxy(null);
        }

        /// <summary>
        /// Gets the XML-RPC proxy object. Fills in certain header values automatically.
        /// </summary>
        /// <param name="server">The server address to use. Includes the "http://" part, but not the api version.</param>
        /// <returns>An IMollomProxy object.</returns>
        private static IMollomProxy GetProxy(string server) {
            IMollomProxy result = XmlRpcProxyGen.Create<IMollomProxy>();
            result.UserAgent = string.Format("NMollom/{0} Mollom/{1}", 
                typeof(Mollom).Assembly.GetName().Version.ToString(4),
                ApiVersion);
            if (!string.IsNullOrEmpty(server)) result.Url = string.Concat(server, "/", ApiVersion);
            return result;
        }

        /// <summary>
        /// The delegate used to perform to the XMLRPC call. This delegate is called
        /// for each server in the serverlist until a call is succesfull.
        /// </summary>
        /// <typeparam name="T">The type of the result object.</typeparam>
        /// <param name="proxy">The mollow proxy object to perform the call to</param>
        /// <param name="auth">The authentication for the mollom call.</param>
        /// <returns>An object of the specified type.</returns>
        private delegate T MollomCall<T>(IMollomProxy proxy, Authentication auth);

        #endregion
    }
}